package cn.zyx.config.shiro;

import cn.zyx.config.shiro.cache.EhCacheCacheManager;
import cn.zyx.config.shiro.session.CacheSessionDAO;
import cn.zyx.config.shiro.session.SessionManager;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.redis.cache.RedisCacheManager;

import javax.servlet.Filter;
import java.util.*;

/**
 * shiro 配置类
 */
@Configuration
public class ShiroConfig {

    /**
     * shiro 过滤器，用来拦截认证和授权请求
     * @param kickoutSessionControlFilter
     * @param securityManager
     * @return
     */
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean factory(KickoutSessionControlFilter kickoutSessionControlFilter,
                                          DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();

        // 添加自己的过滤器并且取名为jwt
        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("jwt", new JWTFilter());
        filterMap.put("kickout", kickoutSessionControlFilter);
        factoryBean.setFilters(filterMap);
        // 设置securityManager
        factoryBean.setSecurityManager(securityManager);
        // 未授权 跳转的url
        factoryBean.setUnauthorizedUrl("/401");

        /*
         * 自定义url规则
         * anon 匿名用户可访问
         * authc 认证用户可访问
         * user 使用remeberMe的用户可访问
         * perms 对应权限可访问
         * role 对应角色可访问
         */
        Map<String, String> filterRuleMap = new LinkedHashMap<>();
        // 访问401和404页面不通过我们的Filter
        filterRuleMap.put("/401", "anon");
        filterRuleMap.put("/sys/login", "anon");
        filterRuleMap.put("/sys/accessKey", "anon");
        filterRuleMap.put( "/sys/refreshToken/**", "anon");
        filterRuleMap.put("/402/1", "anon");
        filterRuleMap.put("/402/2", "anon");
        filterRuleMap.put("/403/1", "anon");
        filterRuleMap.put("/403/2", "anon");
        filterRuleMap.put("/404", "anon");
        //filterRuleMap.put("/systemInfoSocketServer/**", "anon");
        filterRuleMap.put("/sys/sysConfig/**", "anon");
        filterRuleMap.put("/sys/test/**", "anon");
        filterRuleMap.put("/minio/**", "anon");
        // 所有请求通过我们自己的JWT Filter
        filterRuleMap.put("/**", "jwt,kickout");
        // 设置规则
        factoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return factoryBean;
    }

    /**
     * 并发登录控制
     * @return
     * https://blog.csdn.net/yy339452689/article/details/120019271
     */
    @Bean
    public KickoutSessionControlFilter kickoutSessionControlFilter(SessionManager sessionManager,
                                                                   CacheManager cacheManager){
        KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
        //用于根据会话ID，获取会话进行踢出操作的；
        kickoutSessionControlFilter.setSessionManager(sessionManager);
        //使用cacheManager获取相应的cache来缓存用户登录的会话；用于保存用户—会话之间的关系的；
        kickoutSessionControlFilter.setCacheManager(cacheManager);
        //是否踢出后来登录的，默认是false；即后者登录的用户踢出前者登录的用户；
        kickoutSessionControlFilter.setKickoutAfter(false);
        //同一个用户最大的会话数，默认1；比如2的意思是同一个用户允许最多同时两个人登录；
        kickoutSessionControlFilter.setMaxSession(1);
        return kickoutSessionControlFilter;
    }

    /**
     * securityManager 将securityManager交给spring管理
     * @param sessionManager
     * @param cacheManager
     * @return
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager defaultWebSecurityManager(
            SessionManager sessionManager,
            CacheManager cacheManager,
            SystemAuthorizingRealm systemAuthorizingRealm) {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        // 设置sessionManager 会话管理
        defaultWebSecurityManager.setSessionManager(sessionManager);
        // 设置cacheManager 缓存管理
        defaultWebSecurityManager.setCacheManager(cacheManager);
        // securityManager  完成校验需要realm
        //defaultWebSecurityManager.setRealm(systemAuthorizingRealm);
        List<Realm> typeRealms = new ArrayList<>();
        typeRealms.add(systemAuthorizingRealm);
        defaultWebSecurityManager.setRealms(typeRealms);
        return defaultWebSecurityManager;
    }

    /**
     * shiro 对缓存的控制
     * @param ehCacheCacheManager
     * @ConditionalOnProperty 一般是在配置类上或者是@Bean修饰的方法上，添加此注解表示一个类是否要被Spring上下文加载
     * 若满足条件则加载，若不满足条件则不加载。
     * prefix：配置文件中的前缀。
     * name：配置的名字。
     * havingValue：它的值与配置文件的值对比，当两个值相同，类会被加载到spring的IOC容器中。
     * @return
     */
    @ConditionalOnProperty(name = "spring.cache.type", havingValue = "ehcache")
    @Bean
    public CacheManager shiroCacheManager(org.springframework.cache.ehcache.EhCacheCacheManager ehCacheCacheManager){
        // 自定义的ehcache
        EhCacheCacheManager cacheManager = new EhCacheCacheManager();
        cacheManager.setCacheManager(ehCacheCacheManager);
        return cacheManager;
    }

    /*@ConditionalOnProperty(name = "spring.cache.type", havingValue = "redis")
    @Bean
    public CacheManager shiroRedisCacheManager(){
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        return redisCacheManager;
    }*/

    /**
     * session 控制
     * @param dao
     * @return
     */
    @Bean(name = "sessionManager")
    public SessionManager sessionManager(CacheSessionDAO dao) {
        SessionManager sessionManager = new SessionManager();
        sessionManager.setSessionDAO(dao);
        sessionManager.setGlobalSessionTimeout(86400000);
        // 用于设置会话验证间隔的方法，主要用于定期检查会话是否仍然有效
        sessionManager.setSessionValidationInterval(1800000);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        // 服务器在创建会话时，通过HTTP响应头中的Set-Cookie字段发送一个包含会话ID的Cookie。
        sessionManager.setSessionIdCookie(new SimpleCookie("wolfking.zyx.session.id"));
        sessionManager.setSessionIdCookieEnabled(true);
        return sessionManager;
    }

    /**
     * shiro 对权限注解的支持
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
            DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean
    @DependsOn("lifecycleBeanPostProcessor")  // spring会指定在创建bean对象DefaultAdvisorAutoProxyCreator之前，优先创建lifecycleBeanPostProcessor对象
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    /**
     * 用来管理shiro bean的生命周期
     * @return
     */
    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
}
